﻿// -----------------------------------------------------------------------
// <copyright file="LibraryController.cs" company="EPAM Systems">
////Copyright (c) EPAM Systems, 2012. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace MBOP.Website.Controllers
{
	#region Usings

	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Web;
	using System.Web.Mvc;
	using System.Web.UI;

	using MBOP.Core.DTO;
	using MBOP.Configuration.ConfigElements;
	using MBOP.Core.Entities.Common;
	using MBOP.Core.Entities.Library;
	using MBOP.Core.FilterContexts;
	using MBOP.Core.Interfaces.Notification;
	using MBOP.Core.Interfaces.Services;
	using MBOP.DistributedServices;
	using MBOP.Website.CustomAttributes;
	using MBOP.Website.Infrastructure.Mappers;
	using MBOP.Website.ViewModels.Common;
	using MBOP.Website.ViewModels.Library;

	#endregion

	/// <summary>
	/// Provides methods that respond to HTTP request that are made to library part of the application.
	/// </summary>
	public class LibraryController : Controller
	{
		#region Constants

		/// <summary>
		/// Represents book details partial view name.
		/// </summary>
		private const string PARTIAL_VIEW_NAME_BOOK_DETAILS = "BookDetails";

		/// <summary>
		/// Represents list action's name.
		/// </summary>
		private const string LIBRARY_ACTION_LIST = "List";

		#endregion

		#region Private Fields

		/// <summary>
		/// Represents book service.
		/// </summary>
		private IBookService _bookService;

		/// <summary>
		/// Represents person information service.
		/// </summary>
		private IPersonInformationService _personInfoService;

		/// <summary>
		/// Represents book mapper.
		/// </summary>
		private BookMapper _bookMapper;

		/// <summary>
		/// Represents image service.
		/// </summary>
		private IImageService _imageService;

		/// <summary>
		/// Represents notification service.
		/// </summary>
		private INotificationService _notificationService;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="LibraryController"/> class.
		/// </summary>
		/// <param name="bookService">The <see cref="IBookService"/> implementer.</param>
		/// <param name="personInfoService">The <see cref="IPersonInformationService"/> implementer.</param>
		/// <param name="imageService">The <see cref="IImageService"/> implementer.</param>
		/// <param name="notificationService">The <see cref="INotificationService"/> implementer.</param>
		public LibraryController(IBookService bookService, IPersonInformationService personInfoService, IImageService imageService, INotificationService notificationService)
		{
			this._bookService = bookService;
			this._personInfoService = personInfoService;
			this._imageService = imageService;
			this._bookMapper = new BookMapper(this._bookService, this._personInfoService);
			this._notificationService = notificationService;
		}

		#endregion

		#region Actions

		/// <summary>
		/// Processes getting correct count of books request.
		/// </summary>
		/// <param name="page">The page of the books.</param>
		/// <param name="filterType">The books filtering type</param>
		/// <returns>The view result, which encapsulates information of the result processing.</returns>
		[OutputCache(Duration = 600, Location = OutputCacheLocation.Server, VaryByParam = "*")]
		public ViewResult List(int page, FilterType filterType, SortInfo sortInfo)
		{
			int skipCount = AppConfigurationSection.GetInstance().AppModules.LibraryModule.PageSize * (page - 1);
			int takeCount = AppConfigurationSection.GetInstance().AppModules.LibraryModule.PageSize;

			string currentUserIdentity = string.Empty;

			if (this.HttpContext.Request.IsAuthenticated && this.HttpContext.Request.LogonUserIdentity != null)
			{
				currentUserIdentity = this.HttpContext.Request.LogonUserIdentity.Name;
			}

			BookFilterContext bookFilterContext = new BookFilterContext
				{
					FilterType = filterType,
					PersonID = this._personInfoService.GetPersonInfo(currentUserIdentity, PersonFindingType.LogonIdentity, false).ID,
					FiltersConfig = AppConfigurationSection.GetInstance().AppModules.LibraryModule.Filters,
					BookService = this._bookService
				};

			IEnumerable<Book> books = this._bookService.GetBooks(skipCount, takeCount, bookFilterContext, sortInfo);

			IEnumerable<BookViewModel> booksViewModels = this._bookMapper.MapBook(books, ListMode.Hide, ListMode.Hide, Request.LogonUserIdentity.Name);

			int pagesCount = (int)Math.Ceiling((decimal)this._bookService.GetBookCount(bookFilterContext) / AppConfigurationSection.GetInstance().AppModules.LibraryModule.PageSize);

			BooksListViewModel booksListViewModel = new BooksListViewModel()
			{
				Books = booksViewModels,
				CurrentPage = page,
				CurrentFilter = filterType,
				PagesTotal = pagesCount,
				CurrentSortInfo = sortInfo,
				AllowedImageFormats = AppConfigurationSection.GetInstance().ImageFormats.ToString()
			};

			return this.View(LIBRARY_ACTION_LIST, booksListViewModel);
		}

		/// <summary>
		/// Processes the add book request.
		/// </summary>
		/// <param name="bookViewModel">The book view model.</param>
		/// <returns>The redirect result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[AuthorizeAdmin]
		[ValidateInput(false)]
		public ActionResult AddBook([Bind(Exclude = "OwnerName")]BookViewModel bookViewModel)
		{
			ActionResult result = null;

			if (ModelState.IsValid)
			{
				this._bookService.AddBook(this._bookMapper.MapBook(bookViewModel));

				result = this.RedirectToAction(LIBRARY_ACTION_LIST);
			}
			else
			{
				result = this.List(1, FilterType.All, new SortInfo());
			}

			return result;
		}

		/// <summary>
		/// Processes the get preload book request
		/// </summary>
		/// <param name="bookID">The book id.</param>
		/// <returns>The json result, which encapsulates information of the result processing.</returns>
		public JsonResult GetPreloadBook(string bookID)
		{
			AmazonService amazonService = new AmazonService();

			Book book = amazonService.GetBook(bookID);

			JsonResult result = new JsonResult();

			if (book != null)
			{
				this._imageService.AddImage(book.Image);

				string imageID = string.Empty;

				if (book.Image != null)
				{
					imageID = book.Image.ID;
				}

				result = this.Json(new
				{
					Title = book.Title,
					Description = book.Description,
					Authors = book.Authors,
					ISBN = book.ISBN,
					EAN = book.EAN,
					ImageID = imageID
				});
			}

			return result;
		}

		/// <summary>
		/// Processes the delete book request.
		/// </summary>
		/// <param name="bookID">The unique book number.</param>
		/// <returns>The redirect result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[AuthorizeAdmin]
		public RedirectToRouteResult DeleteBook(int bookID)
		{
			Book deletingBook = this._bookService.GetBookByID(bookID);

			if (deletingBook != null)
			{
				//string currentReaderIdentity = this._bookService.GetReader(deletingBook) != null ? this._bookService.GetReader(deletingBook).PersonID : null;

				this._bookService.DeleteBook(bookID);

				// Notification Begin
				//IList<string> personIDCollection = (from subscribtion in this._bookService.GetWaitingList(deletingBook)
				//                                             select subscribtion.PersonID).ToList();

				//IDictionary<string,PersonFindingType> receiversIdentityCollection=new Dictionary<string, PersonFindingType>();

				//if (currentReaderIdentity != null)
				//{
				//    foreach (var personID in personIDCollection)
				//    {
				//        receiversIdentityCollection.Add(personID,PersonFindingType.ID);
				//    }
				//}

				//this._notificationService.HandleEvent(LibraryNotificationEvents.BookState.Deleted, receiversIdentityCollection, new { BookTitle = deletingBook.Title });

				// Notification End
			}

			return this.RedirectToAction(LIBRARY_ACTION_LIST);
		}

		/// <summary>
		/// Processes the update book request.
		/// </summary>
		/// <param name="bookViewModel">The book view model.</param>
		/// <param name="currentPage">The user currentPage</param>
		/// <returns>The redirect result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[AuthorizeAdmin]
		[ValidateInput(false)]
		public RedirectToRouteResult UpdateBook([Bind(Exclude = "OwnerName")]BookViewModel bookViewModel, int currentPage)
		{
			this.TryValidateModel(bookViewModel);

			if (ModelState.IsValid)
			{
				this._bookService.UpdateBook(this._bookMapper.MapBook(bookViewModel));
			}

			return this.RedirectToAction(LIBRARY_ACTION_LIST, new { page = currentPage });
		}

		/// <summary>
		/// Processes the admin add subscription request.
		/// </summary>
		/// <param name="newBookSubscriptionViewModel">The book subscription view model.</param>
		/// <param name="historyListMode">The history list state.</param>
		/// <param name="waitingListMode">THe waiting list state.</param>
		/// <returns>The partial view result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[AuthorizeAdmin]
		public PartialViewResult AddSubscriptionAdmin(BookSubscriptionViewModel newBookSubscriptionViewModel, ListMode historyListMode, ListMode waitingListMode)
		{
			if (ModelState.IsValid)
			{
				if (Request != null && Request.UrlReferrer != null)
				{
					string referrer = Request.UrlReferrer.AbsolutePath;

					if (!string.IsNullOrEmpty(referrer))
					{
						HttpResponse.RemoveOutputCacheItem(referrer);
					}
				}

				BookSubscription mappedSubscription = this._bookMapper.MapBookSubscription(newBookSubscriptionViewModel);

				Book book = this._bookService.GetBookByID(mappedSubscription.BookID);

				BookSubscription currentSubscription = this._bookService.GetCurrentPersonSubscription(book, mappedSubscription);

				IEnumerable<BookSubscription> waitingList = this._bookService.GetWaitingList(book);

				this._bookService.AddSubscription(mappedSubscription);

				Book updatedBook = this._bookService.GetBookByID(mappedSubscription.BookID);

				//// Notification Begin
				#region Old notification comment
				//if (currentSubscription == null || currentSubscription.State != mappedSubscription.State)
				//{
				//IDictionary<string, PersonFindingType> receiversIdentityCollection = new Dictionary<string, PersonFindingType>();

				//receiversIdentityCollection.Add(mappedSubscription.PersonID, PersonFindingType.ID);

				//	switch (newBookSubscriptionViewModel.SubscriptionState)
				//	{
				//        case SubscribeState.Reader:
				//            this._notificationService.HandleEvent(LibraryNotificationEvents.SubscribersListState.MarkedAsCurrentReader, receiversIdentityCollection, new {BookTitle = book.Title});

				//            break;

				//        case SubscribeState.History:
				//            this._notificationService.HandleEvent(LibraryNotificationEvents.SubscribersListState.MarkedAsAlreadyRead, receiversIdentityCollection, new {BookTitle = book.Title});

				//            break;
				//		case SubscribeState.History:
				//		case SubscribeState.Waiting:
				// this._notificationService.HandleEvent(LibraryNotificationEvents.SubscribersListState.MoveToWaitingList, receiversIdentityCollection, new {BookTitle = book.Title});



				//		break;
				//}
				//}

				#endregion

				switch (newBookSubscriptionViewModel.SubscriptionState)
				{
					case SubscribeState.History:
					case SubscribeState.Waiting:
						{
							if (IsRaisedAvailableForReadingEvent(book,updatedBook,mappedSubscription))
							{
								IDictionary<string, PersonFindingType> receiversIdentityCollection = new Dictionary<string, PersonFindingType>();

								BookSubscription waitingPerson = waitingList.FirstOrDefault();

								PersonInfo requestPerson = null;

								if (waitingPerson != null)
								{
									receiversIdentityCollection.Add(waitingPerson.PersonID, PersonFindingType.ID);

									requestPerson = this._personInfoService.GetPersonInfo(waitingPerson.PersonID, PersonFindingType.ID, false);
								}
								else if (mappedSubscription.State == (int)SubscribeState.Waiting)
								{
									receiversIdentityCollection.Add(mappedSubscription.PersonID, PersonFindingType.ID);

									requestPerson = this._personInfoService.GetPersonInfo(mappedSubscription.PersonID, PersonFindingType.ID, false);
								}

								this._notificationService.HandleEvent(LibraryNotificationEvents.BookState.AvailableForReading, receiversIdentityCollection, new { BookTitle = book.Title, RequestUser = requestPerson });
							}
							break;
						}
				}

				//// Notification End
				return this.PartialView(PARTIAL_VIEW_NAME_BOOK_DETAILS, this._bookMapper.MapBookDetails(updatedBook, historyListMode, waitingListMode, Request.LogonUserIdentity.Name));
			}

			return null;
		}

		/// <summary>
		///  Processes the common add subscription request.
		/// </summary>
		/// <param name="newBookSubscriptionViewModel">The new book subscription view model.</param>
		/// <param name="historyListMode">The history list state.</param>
		/// <param name="waitingListMode">The waiting list state.</param>
		/// <returns>The action result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[Authorize]
		public ActionResult AddSubscriptionCommon(BookSubscriptionViewModel newBookSubscriptionViewModel, ListMode historyListMode, ListMode waitingListMode)
		{
			ActionResult result = null;

			if (this.IsAllowedSubscriptionRequest(newBookSubscriptionViewModel))
			{
				newBookSubscriptionViewModel.PersonID = this._personInfoService.GetPersonInfo(this.HttpContext.Request.LogonUserIdentity.Name, PersonFindingType.LogonIdentity, false).ID;

				this.TryValidateModel(newBookSubscriptionViewModel);

				if (ModelState.IsValid)
				{
					result = this.AddSubscriptionAdmin(newBookSubscriptionViewModel, historyListMode, waitingListMode);
				}
			}
			else
			{
				result = new HttpUnauthorizedResult();
			}

			return result;
		}

		/// <summary>
		/// Processes the roll subscription request. 
		/// </summary>
		/// <param name="currentBookSubscription">The current book subscription to roll.</param>
		/// <param name="isUndo">The value indicates that is undo or redo operation</param>
		/// <param name="historyListMode">The history list state.</param>
		/// <param name="waitingListMode">The waiting list state.</param>
		/// <returns>The partial view result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[AuthorizeAdmin]
		public PartialViewResult RollSubscription(BookSubscriptionViewModel currentBookSubscription, bool? isUndo, ListMode historyListMode, ListMode waitingListMode)
		{
			BookSubscription mappedBookSubscription = this._bookMapper.MapBookSubscription(currentBookSubscription);

			if (isUndo.HasValue)
			{
				this._bookService.RollSubscription(mappedBookSubscription, isUndo.Value);
			}

			Book book = this._bookService.GetBookByID(currentBookSubscription.BookID);

			return this.PartialView(PARTIAL_VIEW_NAME_BOOK_DETAILS, this._bookMapper.MapBookDetails(book, historyListMode, waitingListMode, Request.LogonUserIdentity.Name));
		}

		/// <summary>
		/// Processes the delete subscription request.
		/// </summary>
		/// <param name="subscriptionSet">The subscription set.</param>
		/// <param name="bookID">The book unique number.</param>
		/// <param name="historyListMode">The history list state.</param>
		/// <param name="waitingListMode">THe waiting list state.</param>
		/// <returns>The partial view result, which encapsulates information of the result processing.</returns>
		[HttpPost]
		[Authorize]
		public ActionResult DeleteSubscriptions(string subscriptionSet, int bookID, ListMode historyListMode, ListMode waitingListMode)
		{
			ActionResult result = null;

			string deletingPersonID = this._bookService.GetPersonIDBySet(bookID, subscriptionSet);

			if (!string.IsNullOrEmpty(deletingPersonID) && this.IsAllowedDeletingRequest(deletingPersonID, subscriptionSet, bookID))
			{
				Book book = this._bookService.GetBookByID(bookID);

				this._bookService.DeletePersonSubscriptions(subscriptionSet, bookID);

				Book updatedBook = this._bookService.GetBookByID(bookID);

				//// Notification Begin
				//IDictionary<string,PersonFindingType> receiversIdentityCollection = new Dictionary<string, PersonFindingType>();

				//receiversIdentityCollection.Add(deletingPersonID,PersonFindingType.ID);

				//this._notificationService.HandleEvent(LibraryNotificationEvents.SubscribersListState.Removed, receiversIdentityCollection, new { BookTitle = book.Title });

				if (IsRaisedAvailableForReadingEvent(book, updatedBook, null))
				{
					IDictionary<string,PersonFindingType> receiversIdentityCollection = new Dictionary<string, PersonFindingType>();

					IEnumerable<BookSubscription> waitingList = this._bookService.GetWaitingList(updatedBook).ToList();

					PersonInfo requestPerson = null;

					if (waitingList.Any())
					{
						receiversIdentityCollection.Add(waitingList.First().PersonID,PersonFindingType.ID);
						requestPerson = this._personInfoService.GetPersonInfo(waitingList.First().PersonID, PersonFindingType.ID, false);
					}

					this._notificationService.HandleEvent(LibraryNotificationEvents.BookState.AvailableForReading, receiversIdentityCollection, new { BookTitle=updatedBook.Title, RequestUser=requestPerson});
				}

				//// Notification End
				result = this.PartialView(PARTIAL_VIEW_NAME_BOOK_DETAILS, this._bookMapper.MapBookDetails(updatedBook, historyListMode, waitingListMode, Request.LogonUserIdentity.Name));
			}
			else
			{
				result = new HttpUnauthorizedResult();
			}

			return result;
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Checks that the current common subscription request is allowed or not.
		/// </summary>
		/// <param name="bookSubscriptionViewModel">The book subscription view model for request.</param>
		/// <returns>The value indicates the current common request is allowed or not.</returns>
		private bool IsAllowedSubscriptionRequest(BookSubscriptionViewModel bookSubscriptionViewModel)
		{
			Book book = this._bookService.GetBookByID(bookSubscriptionViewModel.BookID);

			return string.IsNullOrEmpty(bookSubscriptionViewModel.SubscriptionSet)
					&& bookSubscriptionViewModel.SubscriptionState == SubscribeState.Waiting
					&& string.IsNullOrEmpty(bookSubscriptionViewModel.PersonID)
					&& this.HttpContext.Request.IsAuthenticated
					&& book != null
					&& this._bookService.CanAddInWaitingList(book, bookSubscriptionViewModel.PersonID);
		}

		/// <summary>
		/// Checks that the current common deleting request is allowed or not.
		/// </summary>
		/// <param name="deletingPersonID">The person's id</param>
		/// <param name="subscriptionSet">The subscription set.</param>
		/// <param name="bookID">The book id.</param>
		/// <returns>The value indicates the current common request is allowed or not.</returns>
		private bool IsAllowedDeletingRequest(string deletingPersonID, string subscriptionSet, int bookID)
		{
			Book book = this._bookService.GetBookByID(bookID);

			BookSubscription currentSubscription = this._bookService.GetCurrentPersonSubscription(book, deletingPersonID, subscriptionSet);

			return (bool)Session["isAdmin"] || (deletingPersonID == currentSubscription.PersonID
					&& currentSubscription.State == (int)SubscribeState.Waiting);
		}

		/// <summary>
		/// Checks that the available for reading event is raised.
		/// </summary>
		/// <param name="book">The book before update.</param>
		/// <param name="updatedBook">The book after update.</param>
		/// <param name="newSubscription">The adding subscription.</param>
		/// <returns></returns>
		private bool IsRaisedAvailableForReadingEvent(Book book, Book updatedBook, BookSubscription newSubscription)
		{
			BookSubscription oldReader = this._bookService.GetReader(book);
			BookSubscription newReader = this._bookService.GetReader(updatedBook);

			IEnumerable<BookSubscription> oldWaitingList = this._bookService.GetWaitingList(book);

			return (oldReader != null && newReader == null)
				   ||
				   (newSubscription!=null && newSubscription.State == (int)SubscribeState.Waiting && newReader == null && !oldWaitingList.Any());
		}

		#endregion
	}
}
